﻿#region using

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using CipherLib.Cipher;
using CipherLib.Exceptions;

#endregion

namespace CipherLib.Cipher.SMIME
{
    /// <summary>
    /// The class proposes the methoths to encypt and decypt a string (to string).
    /// </summary>
    public class CipherSMime : CipherEnvelope, ICipher
    {
        #region members

        string _newLine = Environment.NewLine; //might change due to the environment from which the message was sent...
        string _firstLine = string.Empty;
        string _smime_header = string.Empty;

        int _chunkSize = 76; //Mime length must not be superior to 76 and not inferior 56 (header size).

        /// <summary>
        /// The max numbers of characters per line in which the crypted message is splitted
        /// </summary>
        public int ChunkSize
        {
            get { return _chunkSize; }
            set { _chunkSize = value; }
        }

        /// <summary>
        /// If the first line starts with "@", it contains the original fileName. Discussed to used this in an 
        /// earlier meeting, but revoked later. I leave it nevertheless in the code, since it makes sense...
        /// </summary>
        public string FirstLine
        {
            get { return _firstLine; }
            set { _firstLine = value; }
        }

        /// <summary>
        /// The standard message header. The header can either explicitely declared based on the message 
        /// that has been received. By defaumlt it is used a header from Kyriba that I have received from 
        /// Roland in Deptember
        /// </summary>
        protected string SmimeBase64Header
        {
            get
            {
                if (_smime_header == string.Empty)
                {
                    return AppCommon.CIPHERLIB_SMIME_MIME + NewLine
                    + AppCommon.CIPHERLIB_SMIME_CONTENT_DISPOSITION + NewLine
                    + AppCommon.CIPHERLIB_SMIME_CONTENT_TYPE + NewLine
                    + AppCommon.CIPHERLIB_SMIME_CONTENT_TRANSFER_ENCODING + NewLine + NewLine;
                }
                else
                {
                    return _smime_header;
                }
            }
            set { _smime_header = value; }
        }

        /// <summary>
        /// The template contains the header info with the template code that is replaced by the cryoted
        /// message
        /// </summary>
        protected string SmimeTemplate
        {
            get
            {
                return SmimeBase64Header + AppCommon.CIPHERLIB_SMIME_CODE;
            }
        }

        /// <summary>
        /// New-line character(s), used for the message
        /// </summary>
        protected string NewLine
        {
            get { return _newLine; }
            set { _newLine = value; }
        }

        #endregion

        #region constructor

        public CipherSMime(string subjectName) : base(subjectName) { }

        public CipherSMime(string subjectName, StoreName defaultEncryptStoreName, 
            StoreLocation defaultStoreLocation) :
            base(subjectName, defaultEncryptStoreName, defaultStoreLocation) { }

        #endregion

        #region public methods

        /// <summary>
        /// Encrypts a string into a S/Mime message-string
        /// </summary>
        /// <param name="message">The string to encypt</param>
        /// <returns>The encrypted string in a S/Mime format</returns>
        public override string EncryptString(string message)
        {
            string cryptedContent = base.EncryptString(message);
            cryptedContent = SplitCryptedText(cryptedContent, ChunkSize);
            cryptedContent = SmimeTemplate.Replace(AppCommon.CIPHERLIB_SMIME_CODE, cryptedContent);

            return cryptedContent;
        }

        /// <summary>
        /// Decrypts a s/Mime string.
        /// </summary>
        /// <param name="message">the S/Mime string</param>
        /// <returns>The decrypted message</returns>
        public override string DecryptString(string message)
        {
            TextReader tr = new StringReader(message);

            message = this.getEnvelope(message);
            if (message == null)
            {
                throw new SMIMEException(AppCommon.EX_CIPHERLIB_SMIME_HEADER);
            }
            message = UnsplitCryptedText(message);
            string decryptedText = base.DecryptString(message);

            return decryptedText;
        }

        #endregion

        #region Private Methods

        private string getEnvelope(string smimeMessage)
        {
            string envelope = null;

            using (StringReader sr = new StringReader(smimeMessage))
            {
                while (true)
                {
                    string line = sr.ReadLine();
                    if (line == null)
                    {
                        break;
                    }
                    else if (line.Trim() != string.Empty)
                    {
                        string[] values = line.Split(new char[] { AppCommon.SMIME_KEY_VALUE_SEPERATOR });
                        if (values.Length == 1)
                        {
                            envelope = values[0] + Environment.NewLine + sr.ReadToEnd();
                            break;
                        }
                    }
                }
            }

            return envelope;
        }

        private string SplitCryptedText(string cryptedText, int chunkSize)
        {
            StringBuilder chunkedText = new StringBuilder();
            int sizeTreated = 0;

            while (sizeTreated < cryptedText.Length)
            {
                int size = Math.Min(chunkSize, cryptedText.Length - sizeTreated);
                chunkedText.Append(cryptedText.Substring(sizeTreated, size) + NewLine);
                sizeTreated += size;
            }

            return chunkedText.ToString();
        }

        private string UnsplitCryptedText(string splittedCryptedText)
        {
            StringBuilder sb = new StringBuilder();

            string[] lines = splittedCryptedText.Split(new string[] { NewLine }, 
                StringSplitOptions.None);
            foreach (string line in lines)
            {
                sb.Append(line.Replace(NewLine, string.Empty));
            }

            return sb.ToString();
        }

        #endregion
    }
}